<template>
  <canvas
    :ref="domId"
    class="app-sign-canvas"
    :id="domId"
    @mousedown.prevent.stop="handleMousedown"
    @mousemove.prevent.stop="handleMousemove"
    @mouseup.prevent.stop="handleMouseup"
    @mouseleave.prevent.stop="handleMouseleave"
    @touchstart.prevent.stop="handleTouchstart"
    @touchmove.prevent.stop="handleTouchmove"
    @touchend.prevent.stop="handleTouchend"
  >您的浏览器不支持canvas技术,请升级浏览器!</canvas>
</template>
<script>
export default {
  name: 'sign-canvas',
  model: {
    value: 'image',
    event: 'confirm'
  },
  props: {
    image: {
      required: false,
      type: [String],
      default: null
    },

    options: {
      //配置项
      required: false,
      type: [Object],
      default: () => null
    }
  },
  data() {
    return {
      value: null, //base64
      domId: `sign-canvas-${Math.random().toString(36).substr(2)}`, //生成唯一dom标识
      canvas: null, //canvas dom对象
      context: null, //canvas 画布对象
      dpr: 1,
      config: {
        isFullScreen: false, //是否全屏手写 [Boolean] 可选
        isDpr: false, //是否使用dpr兼容高分屏 [Boolean] 可选
        lastWriteSpeed: 1, //书写速度 [Number] 可选
        lastWriteWidth: 2, //下笔的宽度 [Number] 可选
        lineCap: 'round', //线条的边缘类型 [butt]平直的边缘 [round]圆形线帽 [square]	正方形线帽
        lineJoin: 'round', //线条交汇时边角的类型  [bevel]创建斜角 [round]创建圆角 [miter]创建尖角。
        canvasWidth: document.body.clientWidth, //canvas宽高 [Number] 可选
        canvasHeight: document.body.clientHeight, //高度  [Number] 可选
        bgColor: '#fff', //背景色 [String] 可选 注:这背景色仅仅只是canvas背景,保存的图片仍然是透明的
        writeWidth: 5, //基础轨迹宽度  [Number] 可选
        maxWriteWidth: 30, // 写字模式最大线宽  [Number] 可选
        minWriteWidth: 5, // 写字模式最小线宽  [Number] 可选
        writeColor: '#101010', // 轨迹颜色  [String] 可选
        isSign: true, //签名模式 [Boolean] 默认为非签名模式,有线框, 当设置为true的时候没有任何线框
        imgType: 'png', //下载的图片格式  [String] 可选为 jpeg  canvas本是透明背景的
      },
      resizeTimer: null
    }
  },
  mounted() {
    this.init()
  },

  watch: {
    options: {
      handler() {
        this.init()
      },
      deep: true
    }
  },

  methods: {
    init() {
      const options = this.options
      if (options) {
        for (const key in options) {
          this.config[key] = options[key]
        }
      }
      this.dpr =
        typeof window !== 'undefined' && this.config.isDpr
          ? window.devicePixelRatio ||
            window.webkitDevicePixelRatio ||
            window.mozDevicePixelRatio ||
            1
          : 1
      this.canvas = document.getElementById(this.domId)
      if (!this.canvas) return  // 横竖屏旋转问题
      this.context = this.canvas.getContext('2d')
      this.canvas.style.background = this.config.bgColor

      this.canvasInit()
      this.canvasClear()
    },

    /**
     * 轨迹宽度
     */
    setLineWidth() {
      const nowTime = Date.now()
      const { isSign, writeWidth } = this.config
      //写字模式和签名模式
      if (isSign) {
        this.context.lineWidth = writeWidth * this.dpr
        this.config.lastWriteTime = nowTime
        return
      }
      const { lastWriteTime, minWriteWidth, maxWriteWidth } = this.config
      const diff = nowTime - lastWriteTime
      this.config.lastWriteTime = nowTime

      let calcWidth =
        minWriteWidth + ((maxWriteWidth - minWriteWidth) * diff) / 30

      if (calcWidth < minWriteWidth) {
        calcWidth = minWriteWidth
      } else if (calcWidth > maxWriteWidth) {
        calcWidth = maxWriteWidth
      }
      calcWidth = calcWidth.toFixed(2)

      const { lastWriteWidth } = this.config
      const lineWidth = (this.config.lastWriteWidth =
        (lastWriteWidth / 4) * 3 + calcWidth / 4)
      this.context.lineWidth = lineWidth * this.dpr
    },

    /**
     * 写开始
     */
    writeBegin(point) {
      this.config.isWrite = true
      this.config.lastWriteTime = Date.now()
      this.config.lastPoint = point
      this.writeContextStyle()
    },

    /**
     * 绘制轨迹
     */
    writing(point) {
      this.context.beginPath()
      this.context.moveTo(
        this.config.lastPoint.x * this.dpr,
        this.config.lastPoint.y * this.dpr
      )
      this.context.lineTo(point.x * this.dpr, point.y * this.dpr)
      this.setLineWidth()
      this.context.stroke()
      this.config.lastPoint = point
      this.context.closePath()
    },

    /**
     * 写结束
     */
    writeEnd(point) {
      this.config.isWrite = false
      this.config.lastPoint = point
      this.saveAsImg()
    },

    /**
     * 轨迹样式
     */
    writeContextStyle() {
      this.context.beginPath()
      this.context.strokeStyle = this.config.writeColor
      this.context.lineCap = this.config.lineCap
      this.context.lineJoin = this.config.lineJoin
    },

    /**
     * 清空画板
     */
    canvasClear() {
      this.context.save()
      this.context.strokeStyle = this.config.writeColor
      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)

      this.context.fillStyle = this.config.bgColor
      if (this.config.isFullScreen) {
        this.context.fillRect(0, 0, this.canvas.height, this.canvas.width)
      } else {
        this.context.fillRect(0, 0, this.canvas.width, this.canvas.height)
      }

      this.config.isData = false
      this.$emit('confirm', null)
      this.context.restore()
    },

    /**
     *  保存图片 格式base64
     */
    saveAsImg() {
      const image = new Image()
      image.src = this.canvas.toDataURL()
      this.$emit('confirm', image.src)
      this.config.isData = true
      return image.src
    },

    /**
     * 初始化画板
     */
    canvasInit() {
      const height = this.canvas.offsetHeight || this.config.canvasHeight
      const width = this.canvas.offsetWidth || this.config.canvasWidth
      this.canvas.width = width * this.dpr
      this.canvas.height = height * this.dpr
      this.canvas.style.width = `${width}px`
      this.canvas.style.height = `${height}px`

      if (this.config.isFullScreen) {
        this.context.rotate((-90 * Math.PI) / 180)
        this.context.translate(-height, 0)
      }
    },

    /**
     * 鼠标按下 => 下笔
     */
    handleMousedown(e) {
      this.writeBegin({ x: e.offsetX || e.clientX, y: e.offsetY || e.clientY })
    },

    /**
     * 书写过程 => 下笔书写
     */
    handleMousemove(e) {
      this.config.isWrite && this.writing({ x: e.offsetX, y: e.offsetY })
    },

    /**
     * 鼠标松开 => 提笔
     */
    handleMouseup(e) {
      this.writeEnd({ x: e.offsetX, y: e.offsetY })
    },

    /**
     * 离开书写区域 => 提笔离开
     */
    handleMouseleave(e) {
      this.config.isWrite = false
      this.config.lastPoint = { x: e.offsetX, y: e.offsetY }
    },

    /* ==========================移动端兼容=Start================================ */

    /**
     * 手指按下 => 下笔
     */
    handleTouchstart(e) {
      const touch = e.targetTouches[0]
      const point = this.calcPoint(touch)
      this.writeBegin(point)
    },

    /**
     * 手指移动 => 下笔书写
     */
    handleTouchmove(e) {
      const touch = e.targetTouches[0]
      const point = this.calcPoint(touch)
      this.config.isWrite && this.writing(point)
    },

    /**
     * 手指移动结束 => 提笔离开
     */
    handleTouchend(e) {
      const touch = (e.targetTouches || [])[0] || (e.changedTouches || [])[0]
      const point = this.calcPoint(touch)
      this.writeEnd(point)
    },

    /* ==========================移动端兼容=End================================== */

    /**
     * 下载二维码到本地
     */
    downloadSignImg(name) {
      const c = document.getElementById(this.domId)
      const dataURL = c.toDataURL('image/png')
      this.saveFile(
        dataURL,
        name
          ? `${name}.${this.config.imgType}`
          : `${Date.parse(new Date())}.${this.config.imgType}`
      )
    },

    /**
     * 保存文件
     */
    saveFile(data, filename) {
      const saveLink = document.createElementNS('http://www.w3.org/1999/xhtml','a')
      saveLink.href = data
      saveLink.download = filename
      const event = document.createEvent('MouseEvents')
      event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
      saveLink.dispatchEvent(event);
    },

    calcPoint(touch) {
      const { left, top } = this.canvas.getBoundingClientRect()
      const offset = this.offset(touch.target)
      const { clientX, clientY, pageX, pageY } = touch
      const x = clientX ? clientX - left : pageX - offset.left
      const y = clientY ? clientY - top : pageY - offset.top
      return { x, y }
    },

    /**
     * 获取dom对象的偏移量 可以获取解决position定位的问题
     * @returns number
     */
    offset(target) {
      const { offsetTop, offsetLeft } = target
      const result = { top: offsetTop, left: offsetLeft }

      let parent = target.offsetParent
      while (parent) {
        result.top += parent.offsetTop
        result.left += parent.offsetLeft
        parent = parent.offsetParent
      }
      return result
    },

    /**
     * 图片压缩
     */
    dealImage() {
      //压缩系数0-1之间
      var quality = 0.6
      var canvas = document.createElement('canvas')
      var ctx = canvas.getContext('2d')
      var dealWidth = 300 //目标尺寸
      var dealHeight = 200
      canvas.width = dealWidth
      canvas.width = dealHeight

      // if (Math.max(imgWidth, imgHeight) > w) {
      //     if (imgWidth > imgHeight) {
      //         canvas.width = w
      //         canvas.height = w * imgHeight / imgWidth
      //     } else {
      //         canvas.height = w
      //         canvas.width = w * imgWidth / imgHeight
      //     }
      // } else {
      //     canvas.width = imgWidth
      //     canvas.height = imgHeight
      //     quality = 0.6
      // }
      const c = document.getElementById(this.domId)
      const dataURL = c.toDataURL('image/png')
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      ctx.drawImage(dataURL, 0, 0, canvas.width, canvas.height)
      var ba = canvas.toDataURL('image/jpeg', quality) //压缩语句
    }
  }
}
</script>

